---
description:
  GraphQL Yoga makes it easy to test your GraphQL API. It has built-in support for HTTP injection.
  You can use any testing framework of your choice.
---

import { Callout } from '@theguild/components'

# Testing

GraphQL Yoga makes it easy to test your GraphQL API. It has built-in support for HTTP injection. You
can use any testing framework of your choice.

You can use the `fetch` method on your yoga instance for calling the yoga instance as if you were
doing an HTTP request.

<Callout>
  Calling the `yoga.fetch` method does not send an actual HTTP request. It simulates the HTTP
  request which 100% conforms with how Request/Response work.
</Callout>

## Test Utility

Writing parsers for the Subscription or Incremental Delivery (`@defer`/`@stream`) protocol is
annoying and cumbersome. Instead, you can use the `buildHTTPExecutor` utility function from
`@graphql-tools/executor-http`.

```sh npm2yarn
npm i @graphql-tools/executor-http
```

```ts filename="Building an executor"
import { createSchema, createYoga } from 'graphql-yoga'
import { buildHTTPExecutor } from '@graphql-tools/executor-http'

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      greetings: String!
    }
  `,
  resolvers: {
    Query: {
      greetings: () => 'Hello World!'
    }
  }
})

const yoga = createYoga({ schema })

const executor = buildHTTPExecutor({
  fetch: yoga.fetch
})
```

### Mutation and Query Operations

```ts filename="Mutation and Query Operations"
import { parse } from 'graphql'
import { createSchema, createYoga } from 'graphql-yoga'
import { buildHTTPExecutor } from '@graphql-tools/executor-http'

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      greetings: String!
    }
  `,
  resolvers: {
    Query: {
      greetings: () => 'Hello World!'
    }
  }
})

function assertSingleValue<TValue extends object>(
  value: TValue | AsyncIterable<TValue>
): asserts value is TValue {
  if (Symbol.asyncIterator in value) {
    throw new Error('Expected single value')
  }
}

const yoga = createYoga({ schema })

const executor = buildHTTPExecutor({
  fetch: yoga.fetch,
  endpoint: `http://yoga/graphql`
})

const result = await executor({
  document: parse(/* GraphQL */ `
    query {
      greetings
    }
  `)
})

assertSingleValue(result)

console.assert(
  result.data?.greetings === 'Hello World!',
  `Expected 'Hello World!' but got ${result.data.greetings}`
)
```

### Subscription Operations

```ts filename="Subscription Operations"
import { parse } from 'graphql'
import { createSchema, createYoga, Repeater } from 'graphql-yoga'
import { buildHTTPExecutor } from '@graphql-tools/executor-http'

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      greetings: String!
    }

    type Subscription {
      counter: Int!
    }
  `,
  resolvers: {
    Subscription: {
      counter: {
        subscribe: () =>
          new Repeater((push, end) => {
            let i = 0
            const interval = setInterval(() => {
              push({ counter: i++ })
            }, 100)

            end.then(() => clearInterval(interval))
          })
      }
    }
  }
})

function assertStreamValue<TValue extends object>(
  value: TValue | AsyncIterable<TValue>
): asserts value is AsyncIterable<TValue> {
  if (Symbol.asyncIterator in value) {
    return
  }
  throw new Error('Expected single value')
}

const executor = buildHTTPExecutor({
  fetch: yoga.fetch
})

const result1 = await executor({
  document: parse(/* GraphQL */ `
    subscription {
      counter
    }
  `)
})

const result2 = await executor({
  document: parse(/* GraphQL */ `
    subscription {
      counter
    }
  `)
})

assertStreamValue(result1)
assertStreamValue(result2)

let iterationCounter = 0

for await (const value of result1) {
  if (iterationCounter === 0) {
    console.assert(value.data?.counter === 1, `Expected 1 but got ${value.data?.counter}`)
    iterationCounter++
  } else if (iterationCounter === 1) {
    console.assert(value.data?.counter === 1, `Expected 2 but got ${value.data?.counter}`)
    break
  } else {
    throw new Error('Expected only two iterations')
  }
}
// the server will terminate the iteration after yielding 2 values, for this subscription resolver definition

// on the other hand, for most use cases where we are listening to an indefinitely long stream, we trigger cleanup from the client-side as follows:
// here we demonstrate listening to one value:
const result2It = result2[Symbol.asyncIterator]()
const itResult = await result2It.next()
console.assert(itResult.value.data?.counter === 1)
// after listening to the value, we cleanup by calling return(), if present
await roomUpdatesIterator.return?.()
```

### TypeScript safe Test Suits with GraphQL Code Generator

The GraphQL Code Generator allows you to generate TypeScript types for your GraphQL operations
(queries, mutations and subscriptions), leading to a better developer experience when writing tests.

You can simply pass the documents from GraphQL Code Generator to the `executor` for a fully typed
experience.

```ts filename="TypeScript safe usage of executor"
import { buildHTTPExecutor } from '@graphql-tools/executor-http'
import { graphql } from './gql'
import { yoga } from './yoga'

const HelloWorldQuery = graphql(/* GraphQL */ `
  query HelloWorld {
    hello
  }
`)

const executor = buildHTTPExecutor({
  fetch: yoga.fetch
})

const result = await executor({
  document: HelloWorldQuery
})

// this is now a type-safe when being accessed
// result.data?.hello
```

[Learn more on the GraphQL Code Generator API Testing Guide](https://the-guild.dev/graphql/codegen/docs/guides/api-testing)

## Advanced

Instead of using the convinient `@graphql-tools/executor-http` package you can also process the
request body from scratch.

### Mutation and Query Operation

```ts filename="Query Operation Test"
import { createSchema, createYoga } from 'graphql-yoga'

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      greetings: String!
    }
  `,
  resolvers: {
    Query: {
      greetings: () => 'Hello World!'
    }
  }
})

const yoga = createYoga({ schema })

const response = await yoga.fetch('http://yoga/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: '{ greetings }'
  })
})

console.assert(response.status === 200, 'Response status should be 200')
const executionResult = await response.json()
console.assert(
  executionResult.data?.greetings === 'Hello World!',
  `Expected 'Hello World!' but got ${executionResult.data?.greetings}`
)
```

### Subscription Operation

```ts filename="Query Operation Test"
import { createSchema, createYoga, Repeater } from 'graphql-yoga'

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      greetings: String!
    }

    type Subscription {
      counter: Int!
    }
  `,
  resolvers: {
    Subscription: {
      counter: {
        subscribe: () =>
          new Repeater((push, end) => {
            let i = 0
            const interval = setInterval(() => {
              push({ counter: i++ })
            }, 100)

            end.then(() => clearInterval(interval))
          })
      }
    }
  }
})

const yoga = createYoga({ schema })

function eventStream<TType = unknown>(source: ReadableStream<Uint8Array>) {
  return new Repeater<TType>(async (push, end) => {
    const cancel: Promise<{ done: true }> = end.then(() => ({ done: true }))
    const iterable = source[Symbol.asyncIterator]()
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const result = await Promise.race([cancel, iterable.next()])

      if (result.done) {
        break
      }

      const values = result.value.toString().split('\n\n').filter(Boolean)
      for (const value of values) {
        if (!value.startsWith('data: ')) {
          continue
        }
        const result = value.replace('data: ', '')
        push(JSON.parse(result))
      }
    }

    iterable.return?.()
    end()
  })
}

const response = await yoga.fetch('http://yoga/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: 'subscription { counter }'
  })
})

console.assert(response.status === 200, 'Response status should be 200')
let iterationCounter = 0

for await (const executionResult of eventStream(response.body!)) {
  if (iterationCounter === 0) {
    console.assert(
      executionResult.data?.counter === 1,
      `Expected 1 but got ${executionResult.data?.counter}`
    )
    iterationCounter++
  } else if (iterationCounter === 1) {
    console.assert(
      executionResult.data?.counter === 2,
      `Expected 2 but got ${executionResult.data?.counter}`
    )
    iterationCounter++
  } else if (iterationCounter === 2) {
    console.assert(
      executionResult.data?.counter === 3,
      `Expected 3 but got ${executionResult.data?.counter}`
    )
    break
  } else {
    throw new Error('Expected only three iterations')
  }
}
```
